package com.tyk.bookstore.front.system.service.impl;


import com.tyk.bookstore.front.common.core.model.dto.NameDto;
import com.tyk.bookstore.front.common.core.util.AssertUtil;
import com.tyk.bookstore.front.common.core.util.ObjUtil;
import com.tyk.bookstore.front.common.dal.mapper.dto.BinDto;
import com.tyk.bookstore.front.system.model.dto.AreaFullLevelDto;
import com.tyk.bookstore.front.system.model.dto.AreaSimDto;
import com.tyk.bookstore.front.system.model.entity.AreaDo;
import com.tyk.bookstore.front.system.model.result.AreaResultEnum;
import com.tyk.bookstore.front.system.repository.AreaRepository;
import com.tyk.bookstore.front.system.service.AreaApiService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author tyk
 * @description 区域对内暴露接口
 */
@Service
@RequiredArgsConstructor
public class AreaApiServiceImpl implements AreaApiService {

    private final AreaRepository areaRepository;

    @Override
    public void assertLowestArea(Long code) {
        AssertUtil.isTrue(
                areaRepository.exist1Eq(
                        AreaDo::getCode, code, AreaDo.class
                ),
                AreaResultEnum.AREA_NOT_FOUND
        );
        AssertUtil.isFalse(
                areaRepository.exist1Eq(
                        AreaDo::getParentCode, code, AreaDo.class
                ),
                AreaResultEnum.AREA_NOT_LOWEST
        );
    }

    @Override
    public String getAreaFullNameByCode(Long code) {
        AssertUtil.isTrue(
                areaRepository.exist1Eq(
                        AreaDo::getCode, code, AreaDo.class
                ),
                AreaResultEnum.AREA_NOT_FOUND
        );
        return buildAreaFullName(code);
    }

    @Override
    public List<NameDto> getAreaFullNameByCodeSetWithCache(Set<Long> codeSet) {

        List<NameDto> nameList = new ArrayList<>();
        if (ObjUtil.isEmpty(codeSet)) return nameList;

        Long count = areaRepository.count1In(
                AreaDo::getCode, codeSet, AreaDo.class
        );
        AssertUtil.equal(count, Long.valueOf(codeSet.size()), AreaResultEnum.AREA_NOT_FOUND);

        Map<Long, String> cache = new ConcurrentHashMap<>();
        codeSet.forEach(code -> {
            nameList.add(new NameDto(code, buildAreaFullNameWithCache(code, cache)));
        });

        return nameList;
    }

    @Override
    public List<AreaFullLevelDto> getAreaFullLevelDtoList(Set<Long> codeSet) {
        if (codeSet.isEmpty()) return new ArrayList<>();
        HashMap<Long, AreaFullLevelDto> map = new HashMap<>();
        return codeSet.stream().map(it -> buildAreaFullLevelWithCache(it, map)).toList();
    }

    /**
     * 获取code所属各级别Code和Name
     */
    private AreaFullLevelDto buildAreaFullLevelWithCache(Long code, Map<Long, AreaFullLevelDto> map) {
        if (map.containsKey(code)) return map.get(code);

        BinDto<BigInteger, String> dto = areaRepository.selectBin1Eq(
                AreaDo::getParentCode, AreaDo::getName,
                AreaDo::getCode, code,
                AreaDo.class
        );

        long pc = dto.getV1().longValue();

        AreaFullLevelDto rs;
        if (pc != 0L) {
            AreaFullLevelDto pfd = buildAreaFullLevelWithCache(pc, map);

            ArrayList<Long> codeList = new ArrayList<>(pfd.getCodeList());
            codeList.add(code);
            rs = new AreaFullLevelDto(
                    code,
                    pfd.getFullName() + dto.getV2(),
                    codeList
            );
        } else rs = new AreaFullLevelDto(code, dto.getV2(), List.of(code));

        map.put(code, rs);

        return rs;
    }

    /**
     * 构建区域全称
     */
    private String buildAreaFullNameWithCache(Long code, Map<Long, String> cache) {
        if (cache.containsKey(code)) return cache.get(code);
        AreaSimDto dto = areaRepository.selectAreaSimDtoByCode(code);
        String fullName = dto.getParentCode() == 0 ? dto.getName() : buildAreaFullName(dto.getParentCode()) + dto.getName();
        cache.put(code, fullName);
        return fullName;
    }

    /**
     * 构建区域全称
     */
    private String buildAreaFullName(Long code) {
        BinDto<BigInteger, String> dto = areaRepository.selectBin1Eq(
                AreaDo::getParentCode, AreaDo::getName,
                AreaDo::getCode, code,
                AreaDo.class
        );
        long pid = dto.getV1().longValue();
        return pid == 0 ? dto.getV2() : buildAreaFullName(pid) + dto.getV2();
    }
}
